package top.v5it.logback.push;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.spi.AppenderAttachableImpl;
import top.v5it.logback.push.channel.ChannelPushStrategy;
import top.v5it.logback.push.channel.FailedChannelCallback;
import top.v5it.logback.push.channel.VoidFunc;

import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @author zhanpu
 * @date 2022/9/2
 */
public class PushAppender<E> extends PushAppenderConfig<E> {

    private static final String CHANNEL_LOGGER_PREFIX = ChannelPushStrategy.class.getPackage().getName()
            .replaceFirst("\\.channel$", "");
    private final AppenderAttachableImpl<E> aai = new AppenderAttachableImpl<>();
    private final ConcurrentLinkedQueue<E> queue = new ConcurrentLinkedQueue<>();
    private final FailedChannelCallback<E> failedChannelCallback = (evt, throwable) -> aai.appendLoopOnAppenders(evt);

    @Override
    public void doAppend(E e) {
        ensureDeferredAppends();
        if (e instanceof ILoggingEvent
                && ((ILoggingEvent) e).getLoggerName().startsWith(CHANNEL_LOGGER_PREFIX)) {
            deferAppend(e);
            return;
        }
        super.doAppend(e);
    }

    @Override
    protected void append(E e) {
        final byte[] payload = encoder.encode(e);
        if (convergenceStrategy.isPush(payload)) {
            VoidFunc failedCallback = () -> failedChannelCallback.onFailedChannel(e, null);
            channelPushStrategy.push(pushConfig, payload, failedCallback);
        }
    }

    @Override
    public void start() {
        // only error free appenders should be activated
        if (!checkPrerequisites()) return;
        super.start();
    }

    @Override
    public void addAppender(Appender<E> newAppender) {
        aai.addAppender(newAppender);
    }

    @Override
    public Iterator<Appender<E>> iteratorForAppenders() {
        return aai.iteratorForAppenders();
    }

    @Override
    public Appender<E> getAppender(String name) {
        return aai.getAppender(name);
    }

    @Override
    public boolean isAttached(Appender<E> appender) {
        return aai.isAttached(appender);
    }

    @Override
    public void detachAndStopAllAppenders() {
        aai.detachAndStopAllAppenders();
    }

    @Override
    public boolean detachAppender(Appender<E> appender) {
        return aai.detachAppender(appender);
    }

    @Override
    public boolean detachAppender(String name) {
        return aai.detachAppender(name);
    }

    private void deferAppend(E event) {
        queue.add(event);
    }

    // drains queue events to super
    private void ensureDeferredAppends() {
        E event;
        while ((event = queue.poll()) != null) super.doAppend(event);
    }
}
